#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <string.h>
#include <stdio.h>
#include <pwd.h>
#include <unistd.h>
#include <time.h>
#include <string>

using namespace std;

char *host;
char *port;
bool ascii;

/** Envoyer des signaux ou données
 * @param sock l'identificateur du socket qu'on va utiliser
 * @param buf le buffer q'on va envoyer
 * @param len le longeur du buffer
 * @param flags (0: default, MSG OOB: Out-of-band high priority communication)
 *
 * @Author: txhien - Groupe 5 - Promo15
 */
int sendTimeout(int sock, void *buf,int len, int flags)
{
	//Pour contrôler le temps (timeout)
	struct timeval tv;
	fd_set writefds;

	tv.tv_sec = 20;
	tv.tv_usec = 0;

	FD_ZERO(&writefds);
	FD_SET(sock, &writefds);
	select(sock + 1, NULL, &writefds, NULL, &tv);

	if(!FD_ISSET(sock, &writefds))
		return -1;

	//Envoyer
	return send(sock, buf, len, flags);
}

/** Prendre le code qu'on reçoit du serveur distant
 * @param string le message qu'on reçois
 */
int getCode(char *string)
{
	int result;
	sscanf(string, "%d", &result);
	return result;
}

//Demmander
//cmd : commandement de ftp
/** Envoyer une commande de ftp au serveur distant, les commandes sont décris
 * @param sock l'identificateur du socket qu'on va utiliser
 * @param cmd la commande
 */
int command(int sock, char *cmd)
{
        //Pour contrôler le temps (timeout)
	struct timeval tv;
	fd_set writefds;

	tv.tv_sec = 20;
	tv.tv_usec = 0;

	FD_ZERO(&writefds);
	FD_SET(sock, &writefds);
	select(sock + 1, NULL, &writefds, NULL, &tv);

	if(!FD_ISSET(sock, &writefds))
		return -1;

	//Envoyer
	return send(sock, cmd, strlen(cmd), 0);
}

/** Recevoir des signaux ou données
 * @param sock l'identificateur du socket qu'on va utiliser
 * @param buf le buffer pour archiver les informations qu'on reçoit
 * @param len le longeur du buffer
 * @param flags (0: default, MSG OOB: out-of-bound message, MSG PEEK: look at message without removing)
 */
int recvTimeout(int sock, char *buf, int len, int flags)
{
	//Pour contrôler le temps (timeout)
	struct timeval tv;
	fd_set writefds;

	tv.tv_sec = 20;
	tv.tv_usec = 0;

	FD_ZERO(&writefds);
	FD_SET(sock, &writefds);
	select(sock+1, NULL, &writefds, NULL, &tv);

	if(!FD_ISSET(sock, &writefds))
		return -1;

	//Recevoir
	return recv(sock, buf, len, flags);
}

/** Se connecter à un serveur FTP
 * @param host l'adresse du serveur ftp
 * @param port l'indificateur de la porte de ftp du serveur
 */
int connectToServer(char *host, char *port)
{
	struct sockaddr_in addr;
	struct hostent *h;
	int sock = -1;
	int code;
	printf("Server name: %s -- port = %s\n", host, port);

	//Get l'adresse du serveur
	if (!inet_aton(host, &addr.sin_addr))
	{
		h = gethostbyname(host);
		if (!h)
		{
			herror("gethostbyname");
			return -1;
		}
		else
			addr.sin_addr.s_addr=inet_addr(inet_ntoa(*((struct in_addr *)h->h_addr)));
	}

	//Créer le socket
	if((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)
	{
		herror("socket");
		return -1;
	}

	addr.sin_family=AF_INET;
	addr.sin_port=htons(atoi(port));

	//Connecter
	if(connect(sock, (struct sockaddr *)&addr, sizeof(struct sockaddr)) == -1)
	{
		herror("connect");
		return -1;
	}

	char buf[256];
	memset(buf, 0, 256);
	code = recvTimeout(sock, buf, 256, 0);
	if (code == -1)
	{
		herror("Receive");
		return -1;
	}
	printf("%s", buf);
	return sock;
}

/** Etablir le mode de tranfert
 * @param sock l'identificateur du socket qu'on va utiliser
 * @param isBinary true: établir le mode binaire, false: établir le mode ascii
 */
int binary(int sock,bool isBinary)
{
	ascii = !isBinary;
	char buf[256];
	char cmd[256];
	//mode binaire
	if (isBinary)
	{
		sprintf(cmd, "TYPE I\r\n");
	}
	//mode ascii
	else
	{
		sprintf(cmd, "TYPE A\r\n");
	}

	//envoyer la commande
	command(sock, cmd);

	//recevoir les signaux répondus
	memset(buf, 0, 256);
	if(recvTimeout(sock, buf, 256, 0) != -1)
	{
		printf("binary: %s", buf);
	}
	return 0;
}

/** L'utilisateur s'identifie
 * @param sock l'identificateur du socket qu'on va utiliser
 */
int login(int sock)
{
	char cmd[256];
	char buf[256];
	int code;
	char user[50];
	char *pass = new char[30];
	//Demander
	printf("\nUser: ");
	scanf("%s", user);
	sprintf(cmd, "USER %s\r\n", user);

	command(sock, cmd);

	memset(buf, 0, 256);
	code = recvTimeout(sock, buf, 256, 0);
	printf("\n%s", buf);

	if (code == -1)
	{
		printf("Login refused");
		return -1;
	}
	code = getCode(buf);
	if (code == 530)
	{
		printf("Login refused");
		return -1;
	}
	if (code == 421)
	{
		printf("Timeout");
		return -1;
	}
	if (code == 331)
	{
		pass = getpass("Password: ");
//		printf("\nPassword: ");
//		scanf("%s", pass);
		sprintf(cmd, "PASS %s\r\n", pass);
		command(sock, cmd);

		memset(buf, 0, 256);
		code = recvTimeout(sock, buf, 256, 0);

		if (code > 0)
		{
			printf("%s", buf);
			if (code == -1)
			{
				printf("Login refused");
				return -1;
			}
			code = getCode(buf);
			if (code == 530)
			{
				printf("Login refused");
				return -1;
			}
			if (code == 421)
			{
				printf("Timeout");
				return -1;
			}
			if (code != 230)
			{
				printf("\nSock = %s\n", sock);
				printf("Unknown problem!");
				return -1;
			}
			printf("\nReady\t");
		}
	}

	//étalir la mode ascii par défault
	binary(sock, false);
	return 0;
}

/** Etablir la mode passive pour envoyer des données
 * @param sock l'identificateur du socket qu'on va utiliser
 */
int connectPassive(int sock)
{
	struct sockaddr_in addr;
	char buf[256];
	char ip[15];
	char strPort[4];
	int port[2];
	char *str;
	int code;
	int sockData;

	//Demander
	char *cmd = (char *)"PASV\r\n";
	command(sock, cmd);

	memset(buf, 0, 256);
	code = recvTimeout(sock, buf, 256, 0);
	printf("%s", buf);
	printf("\nCode = %d\n", code);
	if(code == -1)
	{
		printf("\nReceive timeout!\n");
		return -1;
	}

	//Prendre l'adresse du serveur
	str = strchr(buf, '(') + 1;
	int nComma = 0;
	int i = 0;
	memset(ip, 0, 15);
	while (nComma < 4)
	{
		if (str[i] == ',')
		{
			nComma ++;
			if (nComma < 4) ip[i] = '.';
		}
		else ip[i] = str[i];
		i ++;
	}


//	printf("\nip: %s -- i = %d--%d\n", ip, i, ip[i]);

	//Prendre les portes
	str += i * sizeof(char);
	char *str1 = strchr(str, ',');
	str1[0] = 0;
	port[0] = atoi(str);
	str = str1 + sizeof(char);
	str1 = strchr(str, ')');
	str1[0] = 0;

	port[1] = atoi(str);

	//Créer le socket pour prendre des données
	if((sockData=socket(AF_INET, SOCK_STREAM, 0))==-1)	/*get the socket descriptor*/
	{
		herror("socket");
		return -1;
	}

	addr.sin_family=AF_INET;
	addr.sin_addr.s_addr = inet_addr (ip);
	addr.sin_port= htons (256 * port[0] + port[1]);
	bzero(&(addr.sin_zero),8);

	//Connecter
	if(connect(sockData, (struct sockaddr *)&addr, sizeof(struct sockaddr)) == -1)
	{
		herror("connect");
		return -1;
	}


//	printf("\nport %d -- %d\n", port[0], port[1]);
//	memset(buf, 0, 256);
//	if(recv(buf, 256, 0) != -1)
//	{
//		printf("ls: %s", buf);
//	}
	return sockData;
}


/** Afficher le contenu du répertoir courant
 * @param sock l'identificateur du socket qu'on va utiliser
 * @pathname le répertoir courant
 */
int ls(int sock, char *pathname)
{
	int sockData = connectPassive(sock);
	printf("\nsock = %d\n", sockData);

	//Demander
	char *cmd;
	if (pathname == "") cmd = (char *)"LIST\r\n";
	else sprintf(cmd, "LIST %s\r\n", pathname);
	command(sock, cmd);

	char *list = new char[1000];
	int i;
	if ((i = recvTimeout(sock, list, 999, 0)) != 1)
	{
		list[i] = 0;
		printf("ls: %s", list);
	}

	//Recevoir et afficher la liste
	while((i = recvTimeout(sockData, list, 1000, 0)) > 0)
	{
		list[i] = 0;
		printf("%s", list);
	}

	close(sockData);
	if ((i = recvTimeout(sock, list, 999, 0)) != 1)
	{
		list[i] = 0;
		printf("ls: %s", list);
	}
	return 0;
}

/** Changer le répertoire courant
 * @param sock l'identificateur du socket qu'on va utiliser
 * @pathname la destination
 */
int cd(int sock, char *pathname)
{
	char buf[256];
	char cmd[256];
	sprintf(cmd, "CWD %s\r\n", pathname);
	command(sock, cmd);
	memset(buf, 0, 256);
	if(recvTimeout(sock, buf, 256, 0) != -1)
	{
		printf("cd: %s", buf);
	}
	return 0;
}

/** Supprimer un fichier
 * @param sock l'identificateur du socket qu'on va utiliser
 * @pathname le fichier à supprimer
 */
int del(int sock, char *pathname)
{
	char buf[256];
	char cmd[256];

	sprintf(cmd, "DELE %s\r\n", pathname);
	command(sock, cmd);

	memset(buf, 0, 256);
	if(recvTimeout(sock, buf, 256, 0) != -1)
	{
		printf("del: %s", buf);
	}
	return 0;
}

/** Télécharger un fichier
 * @param sock l'identificateur du socket qu'on va utiliser
 * @pathname le fichier à télécharger
 */
int get(int sock, char *fileName)
{

//	command(sock, (char *)"TYPE I\r\n");
	int sockData = connectPassive(sock);
	printf("\nsock = %d\n", sockData);

	//command(sock, (char *)"TYPE I\r\n");
	char *list = new char[1000];
	int i;
	memset(list, 0, 1000);
	//if ((i = recvTimeout(sock, list, 999, 0)) != 1)
	//{
	//	list[i] = 0;
	//	printf("%s", list);
	//}

	char *cmd = new char[256];
	sprintf(cmd, "RETR %s\r\n", fileName);
	command(sock, cmd);

	memset(list, 0, 1000);
	if ((i = recvTimeout(sock, list, 999, 0)) != 1)
	{
		list[i] = 0;
		printf("%s", list);
	}
	time_t begin = time(NULL);


	FILE *file;
	if (ascii)
	{
		file = fopen(fileName, "a");
	}
	else
	{
		file = fopen(fileName, "ab");
	}
	if (file == NULL)
	{
		printf("\nError open file!");
		return -1;
	}

	//Recevoir le fichier
	printf("\nDownloading ...\n");
	while((i = recvTimeout(sockData, list, 999, 0)) > 0)
	{
		if (ascii)
		{
			for (int j = 0 ; j < i; j++) if (list[j] != '\r')
				putc (list[j], file);
		}
		else
		{
			fwrite(list, sizeof(char), i, file);
		}
	}
	close(sockData);
	fclose(file);

	double t = difftime ( time(NULL), begin);
	printf("\nTime execute: %f\n", t);

	memset(list, 0, 999);

        int temp = recvTimeout(sock, list, 256, 0);
	if ( temp != 1)
	{
		list[i] = 0;
		printf("%s", list);
	}
	return 0;
}

/** Téléverser un fichier
 * @param sock l'identificateur du socket qu'on va utiliser
 * @pathname le fichier à téléverser
 */
int put(int sock, char *fileName)
{

	int sockData = connectPassive(sock);
	printf("\nsock = %d\n", sockData);

//	command(sock, (char *)"TYPE I\r\n");
	char *list = new char[1001];
	int i;

//	memset(list, 0, 1000);
//	if ((i = recvTimeout(sock, list, 256, 0)) != 1)
//	{
//		list[i] = 0;
//		printf("%s", list);
//	}

        string tempFile = fileName;
        size_t post = tempFile.find_last_of("/");

        string name = tempFile.substr(post + 1, tempFile.length() - 1);

	char *cmd = new char[256];
	sprintf(cmd, "STOR %s\r\n", name.c_str());
        //strcpy(cmd,"STOR hehe\r\n");
        command(sock, cmd);

	time_t begin = time(NULL);
	memset(list, 0, 1000);
	if ((i = recvTimeout(sock, list, 256, 0)) != 1)
	{
		list[i] = 0;
		printf("%s", list);
	}

	FILE *file;
	if (ascii)
	{
		file = fopen(fileName, "r");
	}
	else
	{
		file = fopen(fileName, "rb");
                //printf("\nOpen File\n");
	}

	//Envoyer le fichier
	printf("\nUploading ...\n");

	while(!feof(file))
	{
		i=sizeof(char) * fread(list, sizeof(char), 1000, file);
		sendTimeout(sockData, list, i, 0);
                //printf("\nGhi file\n");
	}

	close(sockData);
	fclose(file);
	double t = difftime ( time(NULL), begin);
	printf("\nTime execute: %f\n", t);

	memset(list, 0, 1000);

        int temp = recvTimeout(sock, list, 256, 0);

	if(temp != 1)
	{
		list[i] = 0;
		printf("%s", list);
	}
	return 0;
}

/** Téléverser un fichier ou des fichiers
 * @param sock l'identificateur du socket qu'on va utiliser
 * @pathname les fichiers à télécharger
 */
int putList(int sock, char *fileName){
	FILE* fp;
	char listFile [10000];
	memset(listFile,0,10000);

	char cmd [20];
	sprintf(cmd, "ls %s", fileName);
	fp = popen(cmd,"r");
	fread(listFile,1,sizeof(listFile),fp);
	fclose (fp);
	char delims[] = "\n";
	char *res = NULL;
	res = strtok( listFile, delims );
	while( res != NULL )
	{
		if(res != NULL)
		{
			printf( "send \"%s\"\n", res);
			put(sock, res);
		}
		res = strtok( NULL, delims );
	}
	delete res;
	return 0;
}

/** Terminer un utilisateur
 * @param sock l'identificateur du socket
 */
int quit(int sock)
{
	int result = command(sock, (char *)"QUIT\r\n");

	char buf[256];
	memset(buf, 0, 256);
	recvTimeout(sock, buf, 256, 0);
	printf("\n%s", buf);
	return result;
}


/** Créer un répertoire sur le serveur distant
 * @param sock l'identificateur du socket qu'on va utiliser
 * @path le répertoir va être créé
 */
int mkdir(int sock, char *path)
{
	//envoyer la demande
	char cmd[256];
	sprintf(cmd, "MKD %s\r\n", path);
	int result = command(sock, cmd);

	//Recevoir et afficher les réponses
	char buf[256];
	memset(buf, 0, 256);
	recvTimeout(sock, buf, 256, 0);
	printf("%s", buf);
	return result;
}


/** Supprimer un répertoire sur le serveur distant
 * @param sock l'identificateur du socket
 * @path le répertoir à supprimer
 */
int rmdir(int sock, char *path)
{
	//envoyer la demande
	char cmd[256];
	sprintf(cmd, "RMD %s\r\n", path);
	int result = command(sock, cmd);

	//Recevoir et afficher les réponses
	char buf[256];
	memset(buf, 0, 256);
	recvTimeout(sock, buf, 256, 0);
	printf("%s", buf);
	return result;
}

/** Afficher l'aide
 */
void help()
{
	printf("\nGroupe 5 - P15: FTP client \
		\n Syntaxe: tpftp nom_du_serveur [porte] \
		\n  (default port: 21) \
		\n Command: \
		\n -login: s'identifier \
		\n -ascii: établir le mode de tranfert à ascii \
		\n -binary: établir le mode de tranfert à binaire \
		\n -ls: afficher la liste des fichiers et des sous-répertoires\
		\n -cd: change le répertoire courant \
		\n -get: Télécharger des fichiers \
		\n -put: Uploader un fichier \
		\n -putList: Uploader une liste de fichiers \
		\n -del: Supprimer un fichier sur le serveur distant\
		\n -mkdir: créer un répertoire sur le serveur distant \
		\n -rmdir: supprimer un répertoire sur le serveur distant \
		\n -close: fermer la session \
		\n -quit: quiter le logiciel \n");
}

/** Attendre et accepter les commandes d'utilisateur
 * @param sock l'identificateur du socket
 * @isLogin -1: l'utilisateur n'est pas identifié
 */
void waitForCommand(int sock, int &isLogin)
{
	bool cont = true;
	char buf[256];
	char *command = new char[256];
	char *param = new char[256];
	char *param1 = (char *)"";
	printf("\nWait for commands:\n");
	while (cont)
	{
		printf("$ ");
		memset(command, 0, 256);
		memset(param, 0, 256);

		//Prendre la commande et les paramètres
		scanf("%s", command);
		if (getchar() != '\n')
		{
			scanf("%s", param);
			if (getchar() != '\n') scanf("%s", param1);
		}

		if (strcmp(command, "quit") == 0)
		{
			if (sock != -1)
			{
				quit(sock);
				cont = false;
				continue;
			}
			else return;
		}

		if (strcmp(command, "open") == 0)
		{
			if (sock == -1)
			{
				host = param;
				if (param1 == "") port = (char *)"21";
				else port = param1;
				sock = connectToServer(host, port);
				continue;
			}
			else
			{
				printf("\nYou're connected!");
				continue;
			}
		}

		if (strcmp(command, "help") == 0)
		{
			help();
			continue;
		}

		if (sock != -1)
		{
			if (strcmp(command, "login") == 0)
			{
				if (isLogin != -1) printf("You're logged in\n");
				else isLogin = login(sock);
				continue;
			}

			if (isLogin != -1)
			{
				if (strcmp(command, "ls") == 0)
				{
					ls(sock, param);
					continue;
				}
				if (strcmp(command, "cd") == 0)
				{
					cd(sock, param);
					continue;
				}
				if (strcmp(command, "get") == 0)
				{
					get(sock, param);
					continue;
				}
				if (strcmp(command, "put") == 0)
				{
					put(sock, param);
					continue;
				}

				if (strcmp(command, "putList") == 0)
				{
					putList(sock, param);
					continue;
				}
				if (strcmp(command, "mkdir") == 0)
				{
					mkdir(sock, param);
					continue;
				}
				if (strcmp(command, "rmdir") == 0)
				{
					rmdir(sock, param);
					continue;
				}

				if (strcmp(command, "del") == 0)
				{
					del(sock, param);
					continue;
				}

				if (strcmp(command, "binary") == 0)
				{
					binary(sock, true);
					continue;
				}

				if (strcmp(command, "ascii") == 0)
				{
					binary(sock, false);
					continue;
				}

				if (strcmp(command, "close") == 0)
				{
					quit(sock);
//					printf("User logout, login: \n");
//					sock = connectToServer(host, port);
//					login(sock);
					continue;
				}
			}
			else
			{
				printf("\nYou're not logged in!\n");
				continue;
			}
		}
		else
		{
			printf("\nYou're not connected!\n");
			continue;
		}
		printf("Command isn't supported!\n");
	}
}

int main(int argc, char **argv) {
	host = argv[1];
	port = argv[2];
	if (!host) host = (char *)"";
	if (!port) port = (char *)"21";
	int sock = connectToServer(host, port);
	int isLogin;
	if (sock != -1) isLogin = login(sock);
	else isLogin = -1;
	waitForCommand(sock, isLogin);
	close(sock);
	return 1;

}
